OData Producer Library for PHP

 

INDEX

Overview

04

How OData Works: Technology Basics

04

The OData Data-Model

05

Protocol Basics

05

OData Producer for PHP              

06

System Requirements

07

For Windows

07

For Linux

07

Directory Structure

07

OData Service Implementation

07

Implementation of Interfaces

09

IDataServiceMetadata Provider

10

Member Methods

10

Implementing IDataServiceMetadaProvider

11

Creating Metadata

11

Adding Complex Type

13

Adding Stream Data/Property

15

Relationships

17

Building relationship through metadata

19

IDataServiceQuery Provider

19

Member Methods

19

Implementing IDataServiceQueryProvider

20

IDataServiceQueryProvider2

28

Member Methods

28

Implementing IDataServiceQueryProvider2

29

IDataServiceStreamProvider

34

Member Methods

34

Implementing IDataServiceStreamProvider

35

IDataServiceStreamProvider2

37

Member Methods

37

Implementing IDataServiceStreamProvider2

38

IServiceProvider

40

Member Methods

40

Implementing IServiceProvider

40

IExpressionProvider

42

Member Methods

42

Implementing IExpressionProvider

44

Metadata Mapping

55

Member Methods

55

Changes in the service.config file

58

Configuring the OData Service Parameter

59

Member Methods

59

How to set Configuration Parameter – An Example

61

Entity Set Rights

62

Configuring the OData Services

64

Configuring PHP

64

Configuring Web-Server

64

IIS

64

Apache2

65

Database : Installation and Configuration

65

SQL-Server

65

MySQL

66

Installing the NorthWind DB

66

Configuring the library for NorthWind Services

66

Directory Structure

66

Configuring Query Provider to connect to the underlying DB

67

IIS

67

MySQL

67

Creating and configuring the Web-Site(Only for IIS)

67

Making-Sure that Everything is working Fine

68

Accessing the service

69

 

 

Overview

Open Data Protocol is an open protocol for sharing data. It is built upon AtomPub (RFC 5023) and JSON. OData is a REST (Representational State Transfer) protocol, therefore a simple web browser can view the data exposed through an OData service.

The basic idea behind OData is to use a well-known data format (Atom feed) to expose a list of entities. AtomPub extends the basic Atom Protocol by allowing not only read but the whole set of CRUD operations. OData extends AtomPub by enabling simple queries over feeds.

There may be many possible sources of data and many possible clients for one service like Web browsers, apps on mobile devices, business intelligence (BI) tools, and more. How can this varied set of clients access these diverse data sources?

One solution is for every data source to define its own approach to exposing data. It requires every client to contain unique code for each data source it will access, a burden for the people who write those clients. It requires the creators of each data source to specify and implement their own approach to getting at their data, making each one reinvent this wheel.

Defining a common approach makes much more sense. it would make sense to build this technology with existing Web standards as much as possible. OData allows mixing and matching clients and data sources. While it's possible to access an OData data source from an ordinary browser -- the protocol is based on HTTP.

The fundamental idea is that any OData client can access any OData data source. Rather than creating unique ways to expose and access data, data sources and their clients can instead rely on the single solution that OData provides.

How OData Works: Technology Basics

The OData technology has four main parts:

·         The OData data model, which provides a generic way to organize and describe data.

·         The OData protocol, which lets a client make requests to and get responses from an OData service. Data sent by an OData service can be represented on the wire today either in the XML-based format defined by Atom/AtomPub or in JavaScript Object Notation (JSON).

·         OData client libraries, OData clients are applications making OData requests and getting results via the OData protocol.

·         An OData service, which exposes an endpoint that allows access to data. This service implements the OData protocol, and it also uses the abstractions of the OData data model to translate data between its underlying forms.

The OData Data-Model

OData uses the Entity Data Model (EDM).The EDM models data as entities and associations among those entities. Thus OData work with pretty much any kind of data.

Associations between entities can be one-to-one, many-to-one, unidirectional or bi-directional. EDM describes only the logical structure of data.

Protocol Basics

An OData client accesses data provided by an OData service using standard HTTP. The OData protocol largely follows the conventions defined by REST, which define how HTTP verbs are used. The most important of these verbs are:

·         GET                       : Reads data from one or more entities.

·         PUT       : Updates an existing entity, replacing all of its properties.

·         MERGE : Updates an existing entity, but replaces only specified properties.

·         POST     : Creates a new entity.

·         DELETE : Removes an entity.

Each HTTP request is sent to a specific URI, identifying some resource in the target OData service's data model.

OData Producer for PHP

ODataProducer for PHP is a server library which is requires to exposes the data source by using of OData Protocol.

Main components of the ODataProducer are:

1.       Dispatcher

Dispatcher is responsible for resolving the service and delegating the request handling process to the resolved service.

2.       Data Service

The DataService class is the base class for all service specific classes. This class implements two interfaces IRequestHandler and IDataServcie.

3.       OperationContext

The Operation Context classes provide access to HTTP Context of the current request and response.

4.       Object Model Serializer

a.       ATOM

b.      JSON

c.       Metadata

This module is responsible building OData Object Model from Domain Object Model. Domain

Object Model is the result of execution of any Query.

5.       Query Processor

a.       URI Parser

b.      Query Executor

The Query Processor module is responsible for parsing and validation of the URL and execution of the Query.

6.       Response Writer

Response Writer is responsible to set the headers in the Outgoing Response.

OData Producer supports only Read-Only operation specified in Protocol version 2.0:

·         It provides two formats for representing resources, the XML-based Atom format and the JSON format.

·         Servers expose a metadata document that describes the structure of the service and its resources.

·         Clients can retrieve a feed, Entry or service document by issuing an HTTP GET request against its URI.

·         Servers support retrieval of individual properties within Entries.

·         It supports pagination, query validation and system query options like $format, $top, $linecount, $filter, $select, $expand, $orderby, $skip .

·         User can access the binary stream data.

System Requirements

For Windows

1)      IIS,URL Rewrite(http://www.iis.net/download/URLRewrite) / Apache2 with URL rewrite mode

2)      PHP-5.3

For Linux

1)      Apache2  with URL rewrite mode

2)      PHP-5.4

Directory Structure

OData Service Implementation

With using of OData PHP Producer, you can create data services that expose OData feeds. Data in these feeds can come from a variety of data sources.

One of the coolest things about Data Services is its provider model. Any data-source can be exposed as an OData Data Service simply by implementing a few interfaces. Developer can use data providers to expose this data as an OData feed.

The PHP OData Producer framework ships with some internal providers, and makes it possible for developer to create custom providers. The data service can use custom providers for interacting with the underlying Data Source means any data-source can be exposed as an OData Data Service. The provider implementation defines the data model for the service.

We have to implement set of interfaces to expose data by using of  OData Producer:

Provider Interface

Description

IDataServiceMetadataProvider

Framework uses the class which implements this interface to get information about available ResourceTypes, Properties, Keys, NavigationProperties, and ResourceSets.

IDataServiceQueryProvider

Framework uses the class which implements this interface to fulfill all HTTP GET requests.

IDataServiceQueryProvider2

( Optional : If we need some optimization like library should not concern about filtering  then only its implementation is require)

It is same as IDataServiceQueryProvider except  one difference that with IDataServiceQueryProvider library does  the filtering and if result-set is huge then  performance of the library will not up to the marks but with IDataServiceQueryProvider2,library appends the filtering criteria in the SQL query hence library don’t need to scrub out the innecessory records to get the final subset of data.

IDataServiceStreamProvider

(Optional : if entity contains any binary data only)

Framework uses the class which implements this interface to manipulate an underlying stream for Media Link Entries.

IServiceProvider

Informs the framework that the class represents a service with Data Service Providers implementation.

IExpressionProvider

(Optional : External Implementation of IExpressionProvider is require only with IDataServiceQueryProvider2 else library has one internal implementation and end-developer don’t need to implement this interface.)

Framework uses this class to convert filtering criteria into one expression corresponding to the underlying DB.

So the task to write a data service with custom metadata provider support (CMPS) can be divided into the following sub-tasks:

·         Defining proxy classes representing the entities exposed by the data service.

·         Defining a class CreateNorthWindMetaData and method CreateNorthWindMetaData::Create() that uses the methods of ServiceBaseMetadata class to define the shape of the service (container and namespace name, entities and its properties, entity sets and association between entities).

·         Implement a mechanism to get the  the underlying DB means we have to implement either IDataServiceQueryProvider or IDataServiceQueryProvider2 interface.

·         Defining a class which represents the data service (by deriving from framework’s DataService class) and this class also implements IServiceProvider interface.

·         If end-developer implemented IDataServiceQueryProvider2 then we have to implement IExpressionProvider also so that library can convert the filtering criteria into an  expression which would be compatible to underlying data source.

·         If we implemented IDataServiceQueryProvider2  and property names defined in the Metadata are not matching with the names defined in the underlying data source then we have to define one more method CreateNorthWindMetaData::mappingInitialize().

a.       CreateNorthWindMetaData::mappingInitialize() is not required  in few scenarios:

                                                   i.      If we are not  implementing IDataServiceQueryProvider2.

                                                 ii.      If  we are implementing IDataServiceQueryProvider2 but the property and entity names defined in the metadata are same as defined in the data-source and our queries are not using any name alias also for table names.

·         Modifications in the Service.Config.xml file to register the new service with the library.

·         Configuring the URL Rewrite module with IIS/Apache.

a.       Click here to check that how can configure the URL Rewrite mode for IIS/Apache.

Implementation of Interfaces

This section will explain the implementation of all the interfaces to build a data service using OData Producer for PHP.

We will use the NorthWind DB to show the process to create a new OData feed using OData Producer for PHP. Following diagram is the EDM representation of NorthWind DB Which is showing all the entities, their properties and association between entities.

Description: C:\Users\v-nevija\Desktop\NorthWindDB_EDMDiagram.png

Our sample implementation requires only 3 entities: Customer, Order and Order_Detail.

First we have to create a service\<Service-Name> folder to keep the implementation of customized providers for one specific service.

For instance we have to create D:\Projects\ODataPHPProducer\service\NorthWind folder for “NorthWind” service and the newly created folder will contain files for these customized provider.

Library has few sample service implementation under “service” folder and its looks like:

Here folder “service\IDataServiceQueryProvider Implementation\NorthWind” contains the files which are required when user implements IDataServiceQueryProvider interface and folder “service\IDataServiceQueryProvider2 Implementation\NorthWind” contains the files which are required when user implements IDataServiceQueryProvider2 interface.

Here folder “service\IDataServiceQueryProvider Implementation\NorthWind” contain following files:

And folder “service\IDataServiceQueryProvider2 Implementation\NorthWind” contains following files:

IDataServiceMetaDataProvider

IDataServiceMetadataProvider is responsible for describing the shape of the Resources, ResourceSets and ResourceAssociationSet available from your Data Source.

Member Methods

 

Name

Return Type

Description

getContainerName()

String

Get the container name for the data source.

getContainerNamespace()

String

Get the container namespace for the data source.

getResourceSets()

array(ResourceSet)

Get all entity set information.               

getTypes()

array(ResourceType)

Get all resource types in the data source.               

resolveResourceSet ()

ResourceSet/NULL

Get a resource set based on the name specified

resolveResourceType ()

ResourceType/NULL

Get a resource type based on the name specified

getDerivedTypes ()

array(ResourceType)/NULL

Attempts to return all types that derive from the specified resource type.

hasDerivedTypes()

Boolean

Determines whether a resource type has derived types.

getResourceAssociationSet()

ResourceAssociationSet

Gets the ResourceAssociationSet instance when given the source association end.

Implementing IDataServiceMetadataProvider

We don’t need to implement IdataServiceMetada directly because library has one file “Providers\Metadata\ServiceBaseMetadata.php” which actually implements the interface IdataServiceMetadataProvider. We have to use object of ServiceBaseMetadata to define the metadata.

When we asked for the service type IDataServiceMetadataProvider in the implementation of IServiceProvider::getService() it returns instance of ServiceBaseMetadata which implements IDataServiceMetadataProvider.

Creating Metadata

Before proceeding first we try to understand few terminologies:

Resource Type (Entity Type) is the fundamental building block for describing the structure of data with the Entity Data Model (EDM).

A complex type is a template for defining rich, structured properties on entity types or on other complex types.

An entity key is a property or a set of properties of an entity type that are used to determine identity.  The properties that make up an entity key are chosen at design time. The values of entity key properties must uniquely identify an entity type instance within an entity set at run time. The properties that make up an entity key should be chosen to guarantee uniqueness of instances in an entity set.

Resource properties  are the fundamental building blocks of entity types and complex types. This defines the shape and characteristics of data that an entity type instance or complex type instance will contain.

An entity set is a logical container for instances of an entity type and instances of any type derived from that entity type.

First we have to define the class for each entity which is going to be exposed by the service.

Imagine if you have this entity class:

//Order entity type

class Order

{

    //Key Edm.Int32

    public $OrderID;

    //Edm.String

    public $CustomerID;

    //Edm.Int32

    public $EmployeeID;

    //Edm.DateTime

    public $OrderDate;

    //Edm.DateTime

    public $RequiredDate;

    //Edm.DateTime

    public $ShippedDate;

    //Edm.Int32

    public $ShipVia;

    //Edm.Decimal

    public $Freight;

    //Edm.String

    public $ShipName;

    //Edm.String

    public $ShipAddress;

    //Edm.String

    public $ShipCity;

    //Edm.String

    public $ShipRegion;

    //Edm.String

    public $ShipPostalCode;

    public $ShipCountry; //Edm.String

}

We have to define one class for OData service like CreateNorthWindMetadata and has to define method CreateNorthWindMetadata::create().This function will define the metadata structure of that OData service using of methods defined in ServiceBaseMetadata class:

class CreateNorthWindMetadata

{

    /**

     * create metadata

     *

     * @throws InvalidOperationException

     *

     * @return NorthWindMetadata

     */

    public static function create()

    {

        $metadata = new ServiceBaseMetadata('NorthWindEntities', 'NorthWind');

Now we have to define Entity/ResourceType and its properties for Order in method CreateNorthWindMetadata::create() by following way:

        //Register the entity (resource) type 'Order'

        $orderEntityType = $metadata->addEntityType(new ReflectionClass('Order'), 'Order', 'NorthWind');

        $metadata->addKeyProperty($orderEntityType, 'OrderID', EdmPrimitiveType::INT32);

        $metadata->addPrimitiveProperty($orderEntityType, 'CustomerID', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($orderEntityType, 'EmployeeID', EdmPrimitiveType::INT32);

        //Adding an etag property

        $metadata->addETagProperty($orderEntityType, 'OrderDate', EdmPrimitiveType::DATETIME);

        $metadata->addPrimitiveProperty($orderEntityType, 'RequiredDate', EdmPrimitiveType::DATETIME);

        $metadata->addPrimitiveProperty($orderEntityType, 'ShippedDate', EdmPrimitiveType::DATETIME);

        $metadata->addPrimitiveProperty($orderEntityType, 'ShipVia', EdmPrimitiveType::INT32);

        $metadata->addPrimitiveProperty($orderEntityType, 'Freight', EdmPrimitiveType::DECIMAL);

        $metadata->addPrimitiveProperty($orderEntityType, 'ShipName', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($orderEntityType, 'ShipAddress', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($orderEntityType, 'ShipCity', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($orderEntityType, 'ShipRegion', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($orderEntityType, 'ShipPostalCode', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($orderEntityType, 'ShipCountry', EdmPrimitiveType::STRING);

The OrderID is the entity key, so we called ServiceBaseMetadata::addKeyProperty() else we had to call ServiceBaseMetadata::addPrimitiveProperty() or ServiceBaseMetadata::addEtagProperty().

Now we have to add ‘Orders’ as a resource-set:

        $ordersResourceSet = $metadata->addResourceSet('Orders', $orderEntityType);

If you don’t want to expose a property there is no need to add the resource property to the resource type.

Adding Complex types

In the Order entity type all the properties are primitive types, but there are some cases where you want to add complex types. Imagine if you have this entity class:

class Customer

{

    //Key Edm.String

    public $CustomerID;

    //Edm.String

    public $CompanyName;

    //Edm.String

    public $ContactName;

    //Edm.String

    public $Phone;

    //Edm.String

    public $Fax;

    //NorthWind.Address

    public $Address;

    public $EmailAddresses;

    //array(Address)

    public $OtherAddresses;

    //Navigation Property Orders (ResourceSetReference)

    public $Orders;

}

In this Customer entity class the property Address is complex property. The complex class for Address is:

class Address

{

    //Edm.String

    public $StreetName;

    //Edm.String

    public $City;

    //Edm.String

    public $Region;

    //Edm.String

    public $PostalCode;

    //Edm.String

    public $Country;

    //NorthWind.Address

    public $AltAddress;

}

To create the resource type for Customer first you need to register the complex type Address

        //Register the complex type 'Address' having a property of same type.

        $addressComplexType = $metadata->addComplexType(new ReflectionClass('Address'), 'Address', 'NorthWind', null);

        $metadata->addPrimitiveProperty($addressComplexType, 'StreetName', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($addressComplexType, 'City', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($addressComplexType, 'Region', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($addressComplexType, 'PostalCode', EdmPrimitiveType::STRING);         

        $metadata->addPrimitiveProperty($addressComplexType, 'Country', EdmPrimitiveType::STRING);

        //A complex sub property to hold alternate address

        $metadata->addComplexProperty($addressComplexType, 'AltAddress', $addressComplexType);

Please note that In the Address complex type the property AltAddress is again a complex property.

Next register the entity type Customer

        //Register the entity (resource) type 'Customer'

        $customersEntityType = $metadata->addEntityType(new ReflectionClass('Customer'), 'Customer', 'NorthWind');

        $metadata->addKeyProperty($customersEntityType, 'CustomerID', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($customersEntityType, 'CompanyName', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($customersEntityType, 'ContactName', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($customersEntityType, 'ContactTitle', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($customersEntityType, 'Phone', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($customersEntityType, 'Fax', EdmPrimitiveType::STRING);

        $metadata->addComplexProperty($customersEntityType, 'Address', $addressComplexType);

        //Add a bag property (bag of complex type) to hold array of other addresses

        $metadata->addComplexProperty($customersEntityType, 'OtherAddresses', $addressComplexType, true);

        //Add a bag property (bag of primitve type) to hold array of email addresses

        $metadata->addPrimitiveProperty($customersEntityType, 'EmailAddresses', EdmPrimitiveType::STRING, true);

Note the property OtherAddresses is a bag property that hold an array of other addresses.

Now we have to add ‘Customers’ as a resource-set:

         $customersResourceSet = $metadata->addResourceSet('Customers', $customersEntityType);

Adding Stream Data/Property

In the Order entity type all the properties are primitive types and for Customer all the entity types are either Primitive or Complex, but there are some cases where you want to add stream data. Imagine if you have this entity class:

class Employee

{

    //Key Edm.Int32

     public $EmployeeID;

     //Edm.String

     public $FirstName;

     //Edm.String

     public $LastName;

     //Edm.String

     public $Title;

     //Edm.String

     public $TitleOfCourtesy;

     //Edm.DateTime

     public $BirthDate;

     //Edm.DateTime

     public $HireDate;

     //Edm.String

     public $Address;

     //Edm.String

     public $City;

     //Edm.String

     public $Region;

     //Edm.String

     public $PostalCode;

     //Edm.String

     public $Country;

     //Edm.String

     public $HomePhone;

     //Edm.String

     public $Extension;

     //Edm.String

     public $Notes;

     //Bag of strings

     public $Emails;

     //Edm.Int32

     public $ReportsTo;

     //Edm.Binary

     public $Photo;

     //Edm.String

     public $PhotoPath;

}

Here the entity type ‘Employee’ has a property named ‘Photo’ which is a binary data (BLOB type in DB for example) , instance of this field will hold base 64 encoded string.  So you have to add Employee entity as follows:

        //Register the entity (resource) type 'Employee'

        $employeeEntityType = $metadata->addEntityType(new ReflectionClass('Employee'), 'Employee', 'NorthWind');

        $metadata->addKeyProperty($employeeEntityType, 'EmployeeID', EdmPrimitiveType::INT32);

        $metadata->addPrimitiveProperty($employeeEntityType, 'FirstName', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'LastName', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'Title', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'TitleOfCourtesy', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'BirthDate', EdmPrimitiveType::DATETIME);

        $metadata->addPrimitiveProperty($employeeEntityType, 'HireDate', EdmPrimitiveType::DATETIME);

        $metadata->addPrimitiveProperty($employeeEntityType, 'Address', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'City', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'Region', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'PostalCode', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'Country', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'HomePhone', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'Extension', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'Notes', EdmPrimitiveType::STRING);

        $metadata->addPrimitiveProperty($employeeEntityType, 'ReportsTo', EdmPrimitiveType::INT32);

        //$metadata->addPrimitiveProperty($employeeEntityType, 'Photo', EdmPrimitiveType::BINARY);

        $metadata->addPrimitiveProperty($employeeEntityType, 'Emails', EdmPrimitiveType::STRING, true);

        $metadata->addPrimitiveProperty($employeeEntityType, 'PhotoPath', EdmPrimitiveType::STRING);

        //Set Employee entity type as MLE thus the url http://host/NorthWind.svc/Employee(1875)/$value will give the stream associated with employee with id 1875

        $employeeEntityType->setMediaLinkEntry(true);

Now we have to add ‘Employees’ as a resource-set:

         $customersResourceSet = $metadata->addResourceSet('Customers', $customersEntityType);

Relationships

Imagine you have an Order class and a Customer class.

Suppose relationship between these two is one to many i.e. from Order to Customer cardinality is one and from Customer to Order cardinality is many.

Description: C:\Users\v-bibink\Desktop\order_customer.jpg

In this case Order class can contain a property that represents the associated Customer resource (called Resource reference) and Customer can contain a property that represents associated Order resources (called Resource Set reference).

class Customer

{

    //Key Edm.String

    public $CustomerID;

    //Edm.String

    public $CompanyName;

    //Edm.String

    public $ContactName;

    //Edm.String

    public $ContactTitle;

    //Edm.String

    public $Phone;

    //Edm.String

    public $Fax;

    //NorthWind.Address

    public $Address;

    //array(string)

    public $EmailAddresses;

    //array(Address)

    public $OtherAddresses;    //Navigation Property Orders (ResourceSetReference)

    public $Orders;

}

 

 

class Order

{

    //Key Edm.Int32

    public $OrderID;

    //Edm.String

    public $CustomerID;

    //Edm.Int32

    public $EmployeeID;

    //Edm.DateTime

    public $OrderDate;

    //Edm.DateTime

    public $RequiredDate;

    //Edm.DateTime

    public $ShippedDate;

    //Edm.Int32

    public $ShipVia;

    //Edm.Decimal

    public $Freight;

    //Edm.String

    public $ShipName;

    //Edm.String

    public $ShipAddress;

    //Edm.String

    public $ShipCity;

    //Edm.String

    public $ShipRegion;

    //Edm.String

    public $ShipPostalCode;

    //Edm.String

    public $ShipCountry;

    //Navigation Property Customer (ResourceReference)

    public $Customer;

    //Navigation Property Order_Details (ResourceSetReference)

    public $Order_Details;

}

Building relationships through metadata

We have to create the entities first and then we have to build a relationship between them.

First we have to register Order and its primitive properties as explained above and then we have to do it for Customer entity also,as explained above.

The next step is to tell Data Services about order.Customer  and customer.Orders.

        //Register the assoications (navigations)

        //Customers (1) <==> Orders (0-*)

        $metadata->addResourceSetReferenceProperty($customersEntityType, 'Orders', $ordersResourceSet);

        $metadata->addResourceReferenceProperty($orderEntityType, 'Customer', $customersResourceSet);

Notice that order.Customer is a ResourceReference, because there is just one customer, whereas customer.Orders is a ResourceSetReference, because it is a collection.

IDataServiceQueryProvider

The class which implements this interface is used by the framework to perform the query operations on underlying data source.

Member Methods

 

Name

Return Type

Description

getResourceSet ()

array(Object)/array()

Get the collection of entities belongs to an entity set.

getResourceFromResourceSet ()

Object/NULL

Gets an entity instance from an entity set identified by a key.

getResourceFromRelatedResourceSet ()

Object/NULL

Gets a related entity instance from an entity set identified by a key.               

getRelatedResourceSet ()

array(Object)/array()

Get related resource set for a resource.               

getRelatedResourceReference ()

Object/NULL

Get related resource for a resource.

Implementing IDataSrviceQueryProvider

When we asked for the service type IDataServiceQueryProvider in the implementation of IServiceProvider::getService ()  it returns instance of type which implements IDataServiceQueryProvider.

The first step in implementing the IDataServiceQueryProvider is to create a class that implements the interface.

class NorthWindQueryProvider implements IDataServiceQueryProvider

{

Inside the constructor of this class connect to the database as follows:

For SQL-Server:

class NorthWindQueryProvider implements IDataServiceQueryProvider

{

    /**

     * Handle to connection to Database    

     */

    private $_connectionHandle = null;

 

    /**

     * Constructs a new instance of NorthWindQueryProvider

     */

    public function __construct()

    {

        /*

        Connect to the local server using Windows Authentication and specify

        the NorthWind database as the database in use. To connect using

        SQL Server Authentication, set values for the "UID" and "PWD"

        Attributes in the connection info parameter. For example:

        array("UID" => $uid, "PWD" => $pwd, "Database"=>"NorthWind");

        */

        $this->_connectionHandle = sqlsrv_connect(SERVER, array("Database"=> DATABASE));

        if( $this->_connectionHandle ) {             

        } else {            

             die( print_r( sqlsrv_errors(), true));

        }        

    }

}

For MySql:

define('DB_NAME', 'northwind’);

 

/** MySQL database username */

define('DB_USER', 'root');

 

/** MySQL database password */

define('DB_PASSWORD', 'root');

 

/** MySQL hostname */

define('DB_HOST', 'localhost');

class NorthWindQueryProvider implements IDataServiceQueryProvider

{

    /**

     * Handle to connection to Database    

     */

    private $_connectionHandle = null;

 

    /**

     * Constructs a new instance of WordPressQueryProvider

     */

    public function __construct()

    {

        //In case of MySQL we need host, username  and password to connect to DB

        $this->_connectionHandle = @mysql_connect( DB_HOST, DB_USER, DB_PASSWORD, true );

        if( $this->_connectionHandle ) {             

        } else {            

             die( print_r( mysql_error(), true));

        }

        mysql_select_db(DB_NAME, $this->_connectionHandle);

    }

}

Next is to start implementing the methods. There are five methods in IDataServiceQueryProvider that you need to implement.  The following methods are only called when you actually query one of the ResourceSets:

getResourceSet(ResourceSet $resourceSet)

This method implementation should behave in such a way that for the given resource set (entity set); method should return all entities belonging to that resource set.

Example : The query http://localhost/NorthWind.svc/Customers will display all the customers in the NorthWind DB. In this case we retrieve all the customers in the NorthWind DB and serialize the sql/mysql result array in to array list of Customer object as below:

    public function getResourceSet(ResourceSet $resourceSet)

    {  

       

        $resourceSetName =  $resourceSet->getName();

        if ($resourceSetName !== 'Customers'

            && $resourceSetName !== 'Orders'

            && $resourceSetName !== 'Order_Details'

            && $resourceSetName !== 'Employees'

        ) {

            die('(NorthWindQueryProvider) Unknown resource set ' . $resourceSetName);

        }

 

        if ($resourceSetName === 'Order_Details') {

            $resourceSetName = 'Order Details';

        }

 

        $query = "SELECT * FROM [$resourceSetName]";

        $stmt = sqlsrv_query($this->_connectionHandle, $query);

        if ($stmt === false) {

             die(print_r(sqlsrv_errors(), true));

        }

 

        $returnResult = array();

        switch ($resourceSetName) {

        case 'Customers':

            $returnResult = $this->_serializeCustomers($stmt);

            break;

        case 'Orders':

            $returnResult = $this->_serializeOrders($stmt);

            break;

        case 'Order Details':

            $returnResult = $this->_serializeOrderDetails($stmt);

            break;

        case 'Employees':

            $returnResult = $this->_serializeEmployees($stmt);

            break;

        }

 

        sqlsrv_free_stmt($stmt);

        return $returnResult;

    }

 

    /**

     * Serialize the sql result array into Customer objects

     *

     * @param array(array) $result result of the sql query

     *

     * @return array(Object)

     */

    private function _serializeCustomers($result)

    {

        $customers = array();

        while ($record = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC)) {        

             $customers[] = $this->_serializeCustomer($record);

        }

 

        return $customers;

    }   

    /**

     * Serialize the sql row into Customer object

     * @param array $record

     * @return Object

     */

    private function _serializeCustomer($record)

    {

        $customer = new Customer();

        $customer->CustomerID = $record['CustomerID'];

        $customer->CompanyName = $record['CompanyName'];

        $customer->ContactName = $record['ContactName'];

        $customer->Phone = $record['Phone'];

        $customer->Fax = $record['Fax'];       

        $customer->Address = new Address();

        $customer->Address->StreetName = $record['Address'];

        $customer->Address->City = $record['City'];

        $customer->Address->Region = $record['Region'];

        $customer->Address->PostalCode = $record['PostalCode'];

        $customer->Address->Country = $record['Country'];

        //Set alternate address

        $customer->Address->AltAddress = new Address();

        $customer->Address->AltAddress->StreetName = $record['Alt_Address'];

        $customer->Address->AltAddress->City = $record['Alt_City'];

        $customer->Address->AltAddress->Region = $record['Alt_Region'];

        $customer->Address->AltAddress->PostalCode = $record['Alt_PostalCode'];

        $customer->Address->AltAddress->Country = $record['Alt_Country'];

        return $customer;

    }

//Note: Similarly we can serialize other resource sets from the database.

If there are no customers you should return an empty array.

 
getResourceFromResourceSet (ResourceSet $resourceSet, KeyDescriptor $keyDescriptor)

This method implementation should behave in such a way that for the given resource set (entity) and value of key(s), method should return the specific entity in the entity set with the specified key. KeyDescriptor is a type used to represent Key (identifier) for an entity (resource). Entity's identifier is a collection of value for key properties. These values can be named or positional, depending on how they were specified in the URI.

Eg:

·         Named values:  Customers(CustomerID = 'ALFKI'), Order_Details(OrderID=10248,ProductID=11)

·         Positional values: Customers('ALFKI'), Order_Details(10248, 11)

The query http://localhost/NorthWind.svc/Customers(‘ALFKI’) will display the customer with customer id as ALFKI. For this we retrieve only one customer with the CustomerID as ALFKI inside the getResourceFromResourceSet method, and you should return null if such a customer doesn’t exist.

public function getResourceFromResourceSet(ResourceSet $resourceSet, KeyDescriptor $keyDescriptor)

{  

    $resourceSetName =  $resourceSet->getName();

    if ($resourceSetName !== 'Customers'

        && $resourceSetName !== 'Orders'

        && $resourceSetName !== 'Order_Details'

        && $resourceSetName !== 'Products'

        && $resourceSetName !== 'Employees'

     ) {

        die('(NorthWindQueryProvider) Unknown resource set ' . $resourceSetName);   

     }

 

     if ($resourceSetName === 'Order_Details') {

         $resourceSetName = 'Order Details';

     }

 

     $namedKeyValues = $keyDescriptor->getValidatedNamedValues();

     $condition = null;

     foreach ($namedKeyValues as $key => $value) {

        $condition .= $key . ' = ' . $value[0] . ' and ';

     }

 

     $len = strlen($condition);

     $condition = substr($condition, 0, $len - 5);

     $query = "SELECT * FROM [$resourceSetName] WHERE $condition";

     $stmt = sqlsrv_query($this->_connectionHandle, $query);

     if ($stmt === false) {

         die(print_r(sqlsrv_errors(), true));

     }

 

     //If resource not found return null to the library

     if (!sqlsrv_has_rows($stmt)) {

        return null;

     }

 

     $result = null;

     while ( $record = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {

        switch ($resourceSetName) {

            case 'Customers':

                $result = $this->_serializeCustomer($record);

                break;

            case 'Orders':                   

                $result = $this->_serializeOrder($record);

                break;

            case 'Order Details':                   

                $result = $this->_serializeOrderDetail($record);

                break;

            case 'Employees':

                $result = $this->_serializeEmployee($record);

                break;

        }

     }

       

     sqlsrv_free_stmt($stmt);

     return $result;

}

 

**Note: See the serialize function above.

getRelatedResourceSet (ResourceSet $sourceResourceSet, $sourceEntityInstance, ResourceSet $targetResourceSet, ResourceProperty $targetProperty)

The implementation of this method should behave such a way that given the source resource (entity) set, source entity instance, target resource set which is having association with source resource set and name of the property in source resource representing target resource set, method should return the subset target entity set identified by the parameters.

Example: The query http://localhost/NorthWind.svc/Customers(‘ALFKI’)/Orders will display the orders associated with the customer ALFKI. Then in the implementation of getRelatedResourceSet you do something like this:

public function  getRelatedResourceSet(ResourceSet $sourceResourceSet,$sourceEntityInstance,

        ResourceSet $targetResourceSet,

        ResourceProperty $targetProperty

    ) {   

    $result = array();

    $srcClass = get_class($sourceEntityInstance);

    $navigationPropName = $targetProperty->getName();

    if ($srcClass === 'Customer') {

       if ($navigationPropName === 'Orders') {               

           $query = "SELECT * FROM Orders WHERE CustomerID = '$sourceEntityInstance->CustomerID'";

           $stmt = sqlsrv_query($this->_connectionHandle, $query);

           if ($stmt === false) {

               die(print_r(sqlsrv_errors(), true));

           }

           $result = $this->_serializeOrders($stmt);

       } else {

           die('Customer does not have navigation porperty with name: ' . $navigationPropName);

       }           

    } else if ($srcClass === 'Order') {

       if ($navigationPropName === 'Order_Details') {

           $query = "SELECT * FROM [Order Details] WHERE OrderID = $sourceEntityInstance->OrderID";

           $stmt = sqlsrv_query($this->_connectionHandle, $query);

           if ($stmt === false) {           

               die(print_r(sqlsrv_errors(), true));

           }

           $result = $this->_serializeOrderDetails($stmt);

       } else {

           die('Order does not have navigation porperty with name: ' . $navigationPropName);

       }

    }

    return $result;

}

getResourceFromRelatedResourceSet (ResourceSet $sourceResourceSet, $sourceEntityInstance, ResourceSet $targetResourceSet, ResourceProperty $targetProperty, KeyDescriptor $keyDescriptor)

This method should return a related entity instance from an entity set identified by a key. If there is no entity instance is found return null.

Example: The query http://localhost/NorthWind.svc/Customers(‘ALFKI’)/Orders(10643) will display the order identified by the OrderID 10643 of the customer identified by CustomerID ALFKI. The implementation of getResourceFromRelatedResourceSet will be as follows:

    public function  getResourceFromRelatedResourceSet(ResourceSet $sourceResourceSet,

        $sourceEntityInstance,

        ResourceSet $targetResourceSet,

        ResourceProperty $targetProperty,

        KeyDescriptor $keyDescriptor

    ) {

        $result = array();

        $srcClass = get_class($sourceEntityInstance);

        $navigationPropName = $targetProperty->getName();

        $key = null;

        foreach ($keyDescriptor->getValidatedNamedValues() as $keyName => $valueDescription) {

            $key = $key . $keyName . '=' . $valueDescription[0] . ' and ';

        }

 

        $key = rtrim($key, ' and ');

        if ($srcClass === 'Customer') {

            if ($navigationPropName === 'Orders') {               

                $query = "SELECT * FROM Orders WHERE CustomerID = '$sourceEntityInstance->CustomerID' and $key";

                $stmt = sqlsrv_query($this->_connectionHandle, $query);

                if ($stmt === false) {           

                     die(print_r(sqlsrv_errors(), true));

                }

                $result = $this->_serializeOrders($stmt);

            } else {

                die('Customer does not have navigation porperty with name: ' . $navigationPropName);

            }           

        } else if ($srcClass === 'Order') {

            if ($navigationPropName === 'Order_Details') {

                $query = "SELECT * FROM [Order Details] WHERE OrderID = $sourceEntityInstance->OrderID";

                $stmt = sqlsrv_query($this->_connectionHandle, $query);

                if ($stmt === false) {           

                     die(print_r(sqlsrv_errors(), true));

                }

                $result = $this->_serializeOrderDetails($stmt);

            } else {

                die('Order does not have navigation porperty with name: ' . $navigationPropName);

            }

        }

        return empty($result) ? null : $result[0];

    }

getRelatedResourceReference (ResourceSet $sourceResourceSet, $sourceEntityInstance, ResourceSet $targetResourceSet, ResourceProperty $targetProperty)

This method returns related resource for a resource. Return null if there is no resource found.

Example: The query http://localhost/NorthWind.svc/Orders(10643)/Customer will display the customer who placed the order with OrderID 10643. In the implementation of getRelatedResourceReference you do something like this:

public function getRelatedResourceReference(ResourceSet $sourceResourceSet,

     $sourceEntityInstance,

     ResourceSet $targetResourceSet,

     ResourceProperty $targetProperty

) {

     $result = null;

     $srcClass = get_class($sourceEntityInstance);

     $navigationPropName = $targetProperty->getName();

     if ($srcClass === 'Order') {

         if ($navigationPropName === 'Customer') {

             if (empty($sourceEntityInstance->CustomerID)) {

                 $result = null;

             } else {                   

                 $query = "SELECT * FROM Customers WHERE CustomerID = '$sourceEntityInstance->CustomerID'";               

                 $stmt = sqlsrv_query($this->_connectionHandle, $query);

                 if ($stmt === false) {

                     die(print_r(sqlsrv_errors(), true));

                 }

                 if (!sqlsrv_has_rows($stmt)) {

                     $result =  null;

                 }

                 $result = $this->_serializeCustomer(sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC));

                }

            } else {

                die('Customer does not have navigation porperty with name: ' . $navigationPropName);

            }           

        } else if ($srcClass === 'Order_Details') {

            if ($navigationPropName === 'Order') {

                if (empty($sourceEntityInstance->OrderID)) {

                    $result = null;

                } else {

                    $query = "SELECT * FROM Orders WHERE OrderID = $sourceEntityInstance->OrderID";

                    $stmt = sqlsrv_query($this->_connectionHandle, $query);

                    if ($stmt === false) {

                        die(print_r(sqlsrv_errors(), true));

                    }

                    if (!sqlsrv_has_rows($stmt)) {

                        $result =  null;

                    }

                    $result = $this->_serializeOrder(sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC));

            }

        } else {

            die('Order_Details does not have navigation porperty with name: ' . $navigationPropName);

        }

    }

    return $result;

}

IDataServiceQueryProvider2

Implementation of IDataServiceQueryProvider2 interface is optional and it is required only when we are not implementing IDataServiceQueryProvider.

IDataServiceQueryProvider is a basic query provider and library does the filtering to get the required subset of data whereas when we implement IDataServiceQueryProvider2,library use the filtering criteria in the query itself hence library don’t need to do any extra filtering to get the subset of data. 

Member Methods

Name

Parameters

Return Type

Description

getResourceSet ()

$resourceSet(Type: Object of  ResourceSet ,MUST)

$filterOption(Type : String, OPTIONAL)

$orderby(Type : String, OPTIONAL)

$select(Type : String, OPTIONAL)

$top(Type : String, OPTIONAL)

$skip(Type : String, OPTIONAL)

array(Object)/array()

Get the collection of entities belongs to an entity set.

If we need optimized way of query execution then we have to pass filterOptions as string.

Parameters orderBy, select, top, skip are kept for future purpose, no need to pass these parameters as of now.

getResourceFromResourceSet ()

$resourceSet(Type: Object of  ResourceSet ,MUST) $keyDescriptor(Type: Object of   KeyDescriptor ,MUST)

Object/NULL

Gets an entity instance from an entity set identified by a key.

getResourceFromRelatedResourceSet ()

$sourceResourceSet(Type: Object of   ResourceSet,MUST)

        $sourceEntityInstance(Type: Object of   EntityInstance ,MUST)

        $targetResourceSet(Type: Object of   ResourceSet ,MUST)

        $targetProperty(Type: Object of   ResourceProperty ,MUST)

        $keyDescriptor(Type: Object of   KeyDescriptor ,MUST)

Object/NULL

Gets a related entity instance from an entity set identified by a key.               

getRelatedResourceSet ()

$filterOption(Type : String, OPTIONAL)

$orderby(Type : String, OPTIONAL)

$select(Type : String, OPTIONAL)

$top(Type : String, OPTIONAL)

$skip(Type : String, OPTIONAL)

array(Object)/array()

Get related resource set for a resource.    

If we need optimized way of query execution then we have to pass filterOptions as string.

Parameters orderBy, select, top, skip are kept for future purpose, no need to pass these parameters as of now.        

getRelatedResourceReference ()

$sourceResourceSet(Type: Object of   ResourceSet,MUST)

 

$sourceEntityInstance(Type: Object of   EntityInstance ,MUST)

 

$targetResourceSet(Type: Object of   ResourceSet ,MUST)

$targetProperty(Type: Object of   ResourceProperty ,MUST)

Object/NULL

Get related resource for a resource.

Implementing IDataSrviceQueryProvider2

When we asked for the service type IDataServiceQueryProvider2 in the implementation of IServiceProvider::getService() it returns instance of type which implements IDataServiceQueryProvider2.

With IDataServiceQueryProvider2 implementation we have to do minor changes in the getService().Please click here to check the implementation of getService() with IDataServiceQueryProvider2.

The first step in implementing the IDataServiceQueryProvider2 is to create a class that implements the interface.

class NorthWindQueryProvider implements IDataServiceQueryProvider2

{

Inside the constructor of this class connect to the database as follows:

For SQL-Server:

class NorthWindQueryProvider implements IDataServiceQueryProvider2

{

    /**

     * Handle to connection to Database    

     */

    private $_connectionHandle = null;

 

    /**

     * Constructs a new instance of NorthWindQueryProvider

     */

    public function __construct()

    {

        /*

        Connect to the local server using Windows Authentication and specify

        the NorthWind database as the database in use. To connect using

        SQL Server Authentication, set values for the "UID" and "PWD"

        Attributes in the connection info parameter. For example:

        array("UID" => $uid, "PWD" => $pwd, "Database"=>"NorthWind");

        */

        $this->_connectionHandle = sqlsrv_connect(SERVER, array("Database"=> DATABASE));

        if( $this->_connectionHandle ) {             

        } else {            

             die( print_r( sqlsrv_errors(), true));

        }       

    }

}

For MySql:

define('DB_NAME', 'northwind’);

 

/** MySQL database username */

define('DB_USER', 'root');

 

/** MySQL database password */

define('DB_PASSWORD', 'root');

 

/** MySQL hostname */

define('DB_HOST', 'localhost');

class NorthWindQueryProvider implements IDataServiceQueryProvider

{

    /**

     * Handle to connection to Database    

     */

    private $_connectionHandle = null;

 

    /**

     * Constructs a new instance of WordPressQueryProvider

     */

    public function __construct()

    {

        //In case of MySQL we need host, username  and password to connect to DB

        $this->_connectionHandle = @mysql_connect( DB_HOST, DB_USER, DB_PASSWORD, true );

        if( $this->_connectionHandle ) {             

        } else {            

             die( print_r( mysql_error(), true));

        }

        mysql_select_db(DB_NAME, $this->_connectionHandle);

    }

}

Next is to start implementing the methods. There are five methods in IDataServiceQueryProvider2 which we need to implement. 

All methods of IDataServiceQueryProvider2 are same as defined in IDataServiceQueryProvider except following two methods : IDSQP2::getResourceSet() and IDSQP2::getRelatedResourceSet().

In this section we will check the implementation of IDSQP2::getResourceSet() and IDSQP2::getRelatedResourceSet().

These two methods contains few other arguments so that library can fire a more optimized query and hence can avoid extra processing done by library.As of now only filterOption argument is being use in both of these methods and other arguments(select, orderBy, top, skip) are kept for future purpose so no need to pass (select, orderBy, top, skip) parameters as of now.getResourceSet(ResourceSet $resourceSet, $filterOption = null, $select=null, $orderby=null, $top=null, $skip=null)

This method implementation should behave in such a way that for the given resource set (entity set); method should not return all entities belonging to that resource set and it apply the filtering criteria in the query and it should return only required set of data only.

Example : The query http://localhost/NorthWind.svc/Customers will display all the customers in the NorthWind DB. In this case we retrieve all the customers in the NorthWind DB and serialize the sql/mysql result array in to array list of Customer object as below:

    public function getResourceSet(ResourceSet $resourceSet, $filterOption = null,$select=null, $orderby=null, $top=null, $skip=null)

    {  

        $resourceSetName =  $resourceSet->getName();

        if ($resourceSetName !== 'Customers'

            && $resourceSetName !== 'Orders'

            && $resourceSetName !== 'Order_Details'

            && $resourceSetName !== 'Employees'

        ) {

            die('(NorthWindQueryProvider) Unknown resource set ' . $resourceSetName);

        }

        if ($resourceSetName === 'Order_Details') {

            $resourceSetName = 'Order Details';

        }

        $query = "SELECT * FROM [$resourceSetName]";

        if ($filterOption != null) {

            $query .= ' WHERE ' . $filterOption;

        }

        $stmt = sqlsrv_query($this->_connectionHandle, $query);

 

        if ($stmt === false) {

             die(print_r(sqlsrv_errors(), true));

        }

 

        $returnResult = array();

        switch ($resourceSetName) {

        case 'Customers':

            $returnResult = $this->_serializeCustomers($stmt);

            break;

        case 'Orders':

            $returnResult = $this->_serializeOrders($stmt);

            break;

        case 'Order Details':

            $returnResult = $this->_serializeOrderDetails($stmt);

            break;

        case 'Employees':

            $returnResult = $this->_serializeEmployees($stmt);

            break;

        }

 

        sqlsrv_free_stmt($stmt);

        return $returnResult;

    }

 

    /**

     * Serialize the sql result array into Customer objects

     *

     * @param array(array) $result result of the sql query

     *

     * @return array(Object)

     */

    private function _serializeCustomers($result)

    {

        $customers = array();

        while ($record = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC)) {        

             $customers[] = $this->_serializeCustomer($record);

        }

 

        return $customers;

    }   

    /**

     * Serialize the sql row into Customer object

     * @param array $record

     * @return Object

     */

    private function _serializeCustomer($record)

    {

        $customer = new Customer();

        $customer->CustomerID = $record['CustomerID'];

        $customer->CompanyName = $record['CompanyName'];

        $customer->ContactName = $record['ContactName'];

        $customer->ContactTitle = $record['ContactTitle'];

        $customer->Phone = $record['Phone'];

        $customer->Fax = $record['Fax'];       

        $customer->Address = new Address();

        $customer->Address->StreetName = ($record['Address']);

        $customer->Address->City = $record['City'];

        $customer->Address->Region = $record['Region'];

        $customer->Address->PostalCode = $record['PostalCode'];

        $customer->Address->Country = $record['Country'];

        //Set alternate address

        $customer->Address->AltAddress = new Address();

        $customer->Address->AltAddress->StreetName = 'ALT_' . $customer->Address->StreetName;

        $customer->Address->AltAddress->City = 'ALT_' . $customer->Address->City;

        $customer->Address->AltAddress->Region = 'ALT_' . $customer->Address->Region;

        $customer->Address->AltAddress->PostalCode = 'ALT_' . $customer->Address->PostalCode;

        $customer->Address->AltAddress->Country = 'ALT_' . $customer->Address->Country;

        $customer->EmailAddresses = array();

        for ($i = 1; $i < 4; $i++) {

            $customer->EmailAddresses[] = $customer->CustomerID . $i . '@live.com';

        }

 

        $customer->OtherAddresses = array();

        for ($i = 0; $i < 2; $i++) {

            $customer->OtherAddresses[$i] = new Address();

            $this->_copyAddress($customer->Address, $customer->OtherAddresses[$i], $i + 1);

        }

           

        return $customer;

    }

//Note: Similarly we can serialize other resource sets from the database.

If there are no customers you should return an empty array.getRelatedResourceSet (ResourceSet $sourceResourceSet, $sourceEntityInstance, ResourceSet $targetResourceSet, ResourceProperty $targetProperty,$filterOption = null, $select=null, $orderby=null, $top=null, $skip=null)

The implementation of this method should behave such a way that given the source resource (entity) set, source entity instance, target resource set which is having association with source resource set and name of the property in source resource representing target resource set, method should return the subset target entity set identified by the parameters.

Example: The query http://localhost/NorthWind.svc/Customers(‘ALFKI’)/Orders will display the orders associated with the customer ALFKI. Then in the implementation of getRelatedResourceSet you do something like this:

public function  getRelatedResourceSet(ResourceSet $sourceResourceSet,$sourceEntityInstance,

        ResourceSet $targetResourceSet,

        ResourceProperty $targetProperty,

        $filterOption = null, $select=null, $orderby=null, $top=null, $skip=null

    ) {   

        $result = array();

        $srcClass = get_class($sourceEntityInstance);

        $navigationPropName = $targetProperty->getName();

        if ($srcClass === 'Customer') {

            if ($navigationPropName === 'Orders') {               

                $query = "SELECT * FROM Orders WHERE CustomerID = '$sourceEntityInstance->CustomerID'";

                if ($filterOption != null) {

                    $query .= ' AND ' . $filterOption;

                }

                $stmt = sqlsrv_query($this->_connectionHandle, $query);

                if ($stmt === false) {

                     die(print_r(sqlsrv_errors(), true));

                }

 

                $result = $this->_serializeOrders($stmt);

            } else {

                die('Customer does not have navigation porperty with name: ' . $navigationPropName);

            }           

        } else if ($srcClass === 'Order') {

            if ($navigationPropName === 'Order_Details') {

                $query = "SELECT * FROM [Order Details] WHERE OrderID = $sourceEntityInstance->OrderID";

                if ($filterOption != null) {

                    $query .= ' AND ' . $filterOption;

                }

                $stmt = sqlsrv_query($this->_connectionHandle, $query);

                if ($stmt === false) {           

                     die(print_r(sqlsrv_errors(), true));

                }

 

                $result = $this->_serializeOrderDetails($stmt);

            } else {

                die('Order does not have navigation porperty with name: ' . $navigationPropName);

            }

        }

        return $result;

}

IDataServiceStreamProvider

If some stream data is associated to the entity and we want to expose this stream data to the end-user by OData then developer has to implement this interface.

Member Methods

Name

Parameters

Return Type

Description

Comments

getReadStream()

$entity (Type: Object of entity,MUST)

$eTag (Type : String, MUST)

$checkETagForEquality (Type : Boolean, MUST)

$operationContext (Type : Reference of the current context, MUST)

Stream

This method is invoked by the data services framework to retrieve the default stream associated with the entity instance specified by the entity parameter.

An implementer of this method MUST perform concurrency checks.

If the concurrency check passes, this method should return the requested stream else should throw a

DataServiceException.

Null should never be returned from this method.

getStreamContentType()

$entity (Type : Object of entity, MUST)

$operationContext ( Type : Reference of the currentContext, MUST)

String

This method invoked by the data services framework to obtain the content type (media type) of the stream associated with the specified entity.

NIL

getStreamETag()

$entity (Type : Object of entity, MUST)

$operationContext ( Type : Reference of the currentContext, MUST)

String

This method invoked by the data services framework to obtain the ETag of the stream associated with the entity specified.

NIL

getReadStreamUri()

$entity (Type : Object of entity, MUST)

$operationContext ( Type : Reference of the currentContext, MUST)

array(ResourceType)

This method is invoked by the data services framework to obtain the URI clients should use when making retrieve (ie. GET) requests to the stream(ie. Media Resource).

NIL

Implementing IDataServiceStreamProvider

The first step in implementing the IDataServiceStreamProvider is to create a class that implements the interface and we have to define the image path for the services in this class.

Then we have to define the functions to get the eTag, Content-Type and stream associated to the entities

Then we have to define the methods declared in the interface:IDataServiceStreamProvider:

class NorthWindStreamProvider implements IDataServiceStreamProvider

{

    // NOTE: update this path as per your configuration

    const IMAGE_PATH_ROOT = ‘D:\\Projects\\ODataPHPProducer\\services\\NorthWind\\images\\';

 

   /**

    * $eTag :  The etag value sent by the  client (as the value of an  If[-None-]Match header) as part of the HTTP request,  This parameter will be  null if no If[-None-]Match  header was present.

    * $checkETagForEquality :  True if an value of the etag  parameter was sent  to the server as the value  of an If-Match HTTP  request header,  False if an value of the etag  parameter was sent to the  server as the the value  of an If-None-Match HTTP  request header null if  the HTTP request for the  stream was not a  conditional request.

    * $operationContext :  A reference to the context  for the current operation.

    */

    public function getReadStream($entity, $eTag,

        $checkETagForEquality, $operationContext)

    {

        // NOTE: In this impementation we are not checking the eTag equality

        // We will return the stream irrespective of the whether the eTag match of not

        if (!($entity instanceof Employee)) {

            throw new ODataException('Internal Server Error.', 500);

        }

        $filePath = self::IMAGE_PATH_ROOT . 'Employee_' . $entity->EmployeeID

            . '.jpg';

        if (file_exists($filePath)) {

            $handle = fopen($filePath, 'r');

            $stream = fread($handle, filesize($filePath));

            fclose($handle);

            return $stream;

        } else {

            throw new ODataException('The image file could not be found',500);

        }

    }

 

   /**

    * $entity :  The entity instance associated with the stream for which the content type is to be obtained.

    * $operationContext :  A reference to the context  for the current operation.

    */

    public function getStreamContentType($entity, $operationContext)

    {

        if (!($entity instanceof Employee)) {

            throw new ODataException('Internal Server Error.', 500);

        }

        return 'image/jpeg';

    }

 

   /**

    * $entity :  The entity instance associated with the stream for which the content type is to be obtained.

    * $operationContext :  A reference to the context  for the current operation.

    */

    public function getStreamETag($entity, $operationContext)

    {

        if (!($entity instanceof Employee)) {

            throw new ODataException('Internal Server Error.', 500);

        }

        $lastModifiedTime = null;

        $filePath = self::IMAGE_PATH_ROOT . 'Employee_' . $entity->EmployeeID . '.jpg';

        if (file_exists($filePath)) {

            $lastModifiedTime = date("\"m-d-Y H:i:s\"",filemtime($filePath));

        } else {

            return null;

        }

        return $lastModifiedTime;

    }

 

   /**

    * $entity :  The entity instance associated with the stream for which the content type is to be obtained.

    * $operationContext :  A reference to the context  for the current operation.

    */

    public function getReadStreamUri($entity, $operationContext)

    {

        //let library creates default media url.

        return null;

   }

}

IDataServiceStreamProvider2

Implementation of this interface is required for named stream or if multiple streams are associated to one entity and we want to get collection of streams based on the eTag.

Both of the stream provider interface IDataServiceStreamProvider or IDataServiceStreamProvider2 are mutually exclusive and we can implement only one interface at one time.

This interface extends the IDataServiceStreamProvider interface so if developer wants to implement this interface then he has to define those functions also which are declared in the IDataServiceStreamProvider interface.

Member methods

Name

Parameters

Return Type

Description

Comments

getReadStream2 ()

$entity (Type: Object of entity,MUST)

$resourceStreamInfo (Type :ResourceStreamInfo ,MUST)

$eTag (Type : String, MUST)

$checkETagForEquality (Type : Boolean, MUST)

$operationContext (Type : Reference of the current context, MUST)

Stream

This method is invoked by the data services framework to retrieve the named stream associated with the entity instance specified by the entity parameter.

An implementer of this method MUST perform concurrency checks.

If the concurrency check passes, this method should return the requested stream else should throw a

DataServiceException.

Null should never be returned from this method.

If an error occurs while reading the stream, then the data services framework will generate an in-stream error.

getStreamContentType2()

$entity (Type : Object of entity, MUST)

$resourceStreamInfo (Type :ResourceStreamInfo ,MUST)

$operationContext ( Type : Reference of the currentContext, MUST)

String

This method invoked by the data services framework to obtain the content type (media type) of the named stream associated with the specified entity.

NIL

getStreamETag2()

$entity (Type : Object
of entity, MUST)

$resourceStreamInfo (Type :ResourceStreamInfo ,MUST)

$operationContext ( Type : Reference of the currentContext, MUST)

String

This method invoked by the data services framework to obtain the ETag of the named stream associated with the entity specified.

NIL

getReadStreamUri2()

$entity (Type : Object of entity, MUST)

$resourceStreamInfo (Type :ResourceStreamInfo ,MUST)

$operationContext ( Type : Reference of the currentContext, MUST)

array(ResourceType)

This method is invoked by the data services framework to obtain the URI clients should use when making retrieve (ie. GET) requests to the stream(ie. Media Resource).

NIL

Implementing IDataServiceStreamProvider2

The first step in implementing the IDataServiceStreamProvider is to create a class that implements the interface. Then we have to define the functions declared in the interface IDataServiceStreamProvider and IDataServiceStreamProvider2.

class NorthWindStreamProvider implements IDataServiceStreamProvider2

{

   /**

    * $eTag :  The etag value sent by the  client (as the value of an  If[-None-]Match header) as part of the HTTP request,  This parameter will be  null if no If[-None-]Match  header was present.

    * $resourceStreamInfo : The ResourceStreamInfo instance that describes the named stream.

    * $checkETagForEquality :  True if an value of the etag  parameter was sent  to the server as the value  of an If-Match HTTP  request header,  False if an value of the etag  parameter was sent to the  server as the the value  of an If-None-Match HTTP  request header null if  the HTTP request for the  stream was not a  conditional request.

    * $operationContext :  A reference to the context  for the current operation.

    */

    public function getReadStream2($entity, ResourceStreamInfo   $resourceStreamInfo, $eTag, $checkETagForEquality, $operationContext)

    {

        if (!($entity instanceof Employee)) {

            throw new ODataException('Internal Server Error.', 500);

        }       

        $filePath = self::IMAGE_PATH_ROOT . 'Employee_' . $entity->EmployeeID

            . '_' . $resourceStreamInfo->getName() . '.png';

       if (file_exists($filePath)) {

           $handle = fopen($filePath, 'r');

           $stream = fread($handle, filesize($filePath));

           fclose($handle);

           return $stream;

       } else {

           throw new ODataException('The image file could not be found', 500);

       }

    }

 

   /**

    * $resourceStreamInfo : The ResourceStreamInfo instance that describes the named stream.

    * $operationContext :  A reference to the context  for the current operation.

    */

    public function getStreamContentType2($entity, ResourceStreamInfo $resourceStreamInfo,$operationContext)

    {

        if (!($entity instanceof Employee)) {

            throw new ODataException('Internal Server Error.', 500);

        }

    return 'image/png';

    }

   /**

    * $resourceStreamInfo : The ResourceStreamInfo instance that describes the named stream.

    * $operationContext :  A reference to the context  for the current operation.

    */

    public function getStreamETag2($entity, ResourceStreamInfo $resourceStreamInfo, $operationContext)

    {

        return null;

    }

 

   /**

    * $resourceStreamInfo : The ResourceStreamInfo instance that describes the named stream.

    * $operationContext :  A reference to the context  for the current operation.

    */

    public function getReadStreamUri2($entity, ResourceStreamInfo $resourceStreamInfo,$operationContext)

    {

        return null;

    }

}

IDataServiceStreamProvider2 extends the IDataServiceStreamProvider so we have to define methods of the IDataServiceStreamProvider interface also as explained in the section:

IServiceProvider

IServiceProvider is the mechanism for retrieving a service object. End-Developer has to write a new class for each different data service and this class should implement this interface.

Member Methods

Name

Parameters

Return Type

Description

 getService()

$serviceType(Type:String,  MUST)

Reference of MetadataProvider,  QueryProvider,StreamProvider or NULL if parameter is invalid

It provides the instance of MetadataProvider, QueryProvider or StreamProviders.

Implementing IServiceProvider

The first step in implementing the IServiceProvider is to create a class that implements the interface.

Note : This class should extend the DataService class also.

Then we have to define getService method of the IserviceProvider interface in class NorthWindDataService which should return the instance of MetadataProvider, QueryProvider or StreamProvider and we can also define initializeService function in class NorthWindDataService to set the service level configuration.

class NorthWindDataService extends DataService implements IServiceProvider

{

    public function getService($serviceType)

    {

        if ($serviceType === 'IDataServiceMetadataProvider') {

            if (is_null($this->_northWindMetadata)) {

                $this->_northWindMetadata = CreateNorthWindMetadata::create();

            }

            return $this->_northWindMetadata;

        } else if ($serviceType === 'IDataServiceQueryProvider') {

            if (is_null($this->_northWindQueryProvider)) {

                $this->_northWindQueryProvider = new NorthWindQueryProvider();

            }

            return $this->_northWindQueryProvider;

        } else if ($serviceType === 'IDataServiceStreamProvider') {

            return new NorthWindStreamProvider();

        }

        return null;

    }

}

If we implemented IDataServiceQueryProvider2 then few changes are required in the above implementation:

    /**

     * Get the service like IDataServiceMetadataProvider, IDataServiceQueryProvider,

     * IDataServiceStreamProvider

     *

     * @param String $serviceType Type of service IDataServiceMetadataProvider,

     *                            IDataServiceQueryProvider,

     *                            IDataServiceStreamProvider

     *

     * @see library/ODataProducer/ODataProducer.IServiceProvider::getService()

     * @return object

     */

    public function getService($serviceType)

    {

      if(($serviceType === 'IDataServiceMetadataProvider') ||

            ($serviceType === 'IDataServiceQueryProvider2') ||

            ($serviceType === 'IDataServiceStreamProvider')) {

            if (is_null($this->_ northWindMetadata )){

                        $this->_northWindExpressionProvider = new NorthWindDSExpressionProvider();                     

            }   

      }

        if ($serviceType === 'IDataServiceMetadataProvider') {

            if (is_null($this->_northWindMetadata)) {

                $this->_northWindMetadata = CreateNorthWindMetadata::create();

                $this->_northWindMetadata->mappedDetails = CreateNorthWindMetadata::mappingInitialize();

            }

            return $this->_northWindMetadata;

        } else if ($serviceType === 'IDataServiceQueryProvider2') {

            if (is_null($this->_northWindQueryProvider)) {

                $this->_northWindQueryProvider = new WordPressQueryProvider();

            }

            return $this->_northWindQueryProvider;

        } else if ($serviceType === 'IDataServiceStreamProvider') {

            return new NorthWindStreamProvider();

        }

        return null;

    }

IExpressionProvider

Implementation of IExpressionProvider is require only for IDataServiceQueryProvider2 because library needs one expression for the filtering criteria so that library can use that expression in the SQL-Query  for the underlying DB.

Implementation of this interface depends on the underlying DB and its implementation may vary from one DB to another DB.

Actually library parses the complete URL and saves in different kind of data-structures and one is Expression-Tree. This interface implementation is require to traversal that expression –tree so that library can generate an expression which should be compatible to the underlying data source so that library can use that newly generated expression in a SQL query to populate an optimized query so that library don’t need to handle the filtering from itself.

 
Member methods

Name

Parameters

Return Type

Description

Comments

getIteratorName ()

Void

String

Get the name of the iterator.

 

Infect this method is not useful for explicit implementation.

 

Library has one default implementation of IExpressionProvider and that method is require only for that default implementation.

 

Hence user has to declare the function prototype only and no need to define any step inside this function.

onLogicalExpression()

$expressionType (Type : String, MUST)

$left (Type :String ,MUST)

$right (Type : String, MUST)

String

Call back for logical expression

Generate binary expression for logical operators

Implementation of all these function would be almost same for each data-source.

 

These functions basically replaces OData operator by corresponding operator available in data-source so we just need to make sure that the constants which we defined in Step-2 are proper for underlying DB then function definition would be same for each data-source.

onArithmaticExpression()

$expressionType (Type : String, MUST)

$left (Type :String ,MUST)

$right (Type : String, MUST)

String

Call back for arithmetic expression

Generate binary expression for arithmetic operators

onRelationalExpression()

$expressionType (Type : String, MUST)

$left (Type :String ,MUST)

$right (Type : String, MUST)

String

Call back for relational  expression

Generate binary expression for relational operators

onUnaryExpression()

$expressionType (Type : String, MUST)

$child (Type :String ,MUST)

String

Call back for unary expression

Generate expression for unary operators

onConstantExpression()

$type (Type: IType object,MUST)

$value (Type: String,MUST)

String

Call back for constant expression

Returns the constants value

This function definition also would be same for each data source.

onPropertyAccessExpression

$expression(Type: String,MUST)

String

Call back for property access expression

NIL

onFunctionCallExpression()

$functioanDescription (Type : Object of Function-Description  class,MUST)

$params(Type: array of operands,MUST)

String

Call back for function call expression.

Implementation of this function will vary for each different data source.

 

This function will return corresponding function-call string (available in underlying DB) for each OData function call.

Implementing IExpressionProvider

The first step in implementing the IExpressionProvider is to create a class that implements the interface.

class NorthWindDSExpressionProvider implements IExpressionProvider

{

 

 

Second step is to define few constants in this class,one constant for each kind of operator which is available in the underlying DB:

    const ADD                  = '+';

    const CLOSE_BRACKET        = ')';

    const COMMA                = ',';

    const DIVIDE               = '/';

    const SUBTRACT             = '-';

    const EQUAL                = '=';

    const GREATERTHAN          = '>';

    const GREATERTHAN_OR_EQUAL = '>=';

    const LESSTHAN             = '<';

    const LESSTHAN_OR_EQUAL    = '<=';

    const LOGICAL_AND          = 'AND';

    const LOGICAL_NOT          = '!';

    const LOGICAL_OR           = 'OR';

    const MEMBERACCESS         = '';

    const MODULO               = '%';

    const MULTIPLY             = '*';

    const NEGATE               = '-';

    const NOTEQUAL             = '!=';

    const OPEN_BRAKET          = '(';

Then we have to define the methods declared in the interface:IExpressionProvider:

    /**

     * Constructs new instance of MySQLExpressionProvider

     *

     * @param string $iterName The name of the iterator

     */

    public function __construct()

    {

        //$this->_iterName = $iterName;

    }

 

    /**

     * Get the name of the iterator

     *

     * @return string

     */

    public function getIteratorName()

    {

        //return $this->_iterName;

    }

 

    /**

     * Call-back for constant expression

     *

     * @param IType  $type  The type of constant

     * @param objetc $value The value of the constant

     *

     * @return string

     */

    public function onConstantExpression(IType $type, $value)

    {

        return $value;

    }

onLogicalExpression($expressionType, $left, $right)

    /**

     * Call-back for logical expression

     *

     * @param ExpressionType $expressionType The type of logical expression.

     * @param string         $left           The left expression.

     * @param string         $right          The left expression.

     *

     * @return string

     */

    public function onLogicalExpression($expressionType, $left, $right)

    {

        switch($expressionType) {

        case ExpressionType::AND_LOGICAL:

            return $this->_prepareBinaryExpression(self::LOGICAL_AND, $left, $right);

            break;

        case ExpressionType::OR_LOGICAL:

            return $this->_prepareBinaryExpression(self::LOGICAL_OR, $left, $right);

            break;

        default:

            throw new \InvalidArgumentException('onLogicalExpression');

        }

    }

 

This function replaces all the logical operators (used in the filtering criteria) by corresponding logical operators available in the underlying data-source.

onArithmeticExpression($expressionType, $left, $right)

This function replaces all the arithmetic operators(used in the filtering criteria)  by corresponding arithmetic operators available in the underlying data-source.

    /**

     * Call-back for arithmetic expression

     *

     * @param ExpressionType $expressionType The type of arithmetic expression.

     * @param string         $left           The left expression.

     * @param string         $right          The left expression.

     *

     * @return string

     */

    public function onArithmeticExpression($expressionType, $left, $right)

    {

        switch($expressionType) {

        case ExpressionType::MULTIPLY:

            return $this->_prepareBinaryExpression(self::MULTIPLY, $left, $right);

            break;

        case ExpressionType::DIVIDE:

            return $this->_prepareBinaryExpression(self::DIVIDE, $left, $right);

            break;

        case ExpressionType::MODULO:

            return $this->_prepareBinaryExpression(self::MODULO, $left, $right);

            break;

        case ExpressionType::ADD:

            return $this->_prepareBinaryExpression(self::ADD, $left, $right);

            break;

        case ExpressionType::SUBTRACT:

            return $this->_prepareBinaryExpression(self::SUBTRACT, $left, $right);

            break;

        default:

            throw new \InvalidArgumentException('onArithmeticExpression');

        }

    }

 

onRelationalExpression($expressionType, $left, $right)

This function replaces all the relational operators (used in the filtering criteria)  by corresponding relational operators available in the underlying data-source.

    /**

     * Call-back for relational expression

     *

     * @param ExpressionType $expressionType The type of relation expression

     * @param string         $left           The left expression

     * @param string         $right          The left expression

     *

     * @return string

     */

    public function onRelationalExpression($expressionType, $left, $right)

    {

        switch($expressionType) {

        case ExpressionType::GREATERTHAN:

            return $this->_prepareBinaryExpression(self::GREATERTHAN, $left, $right);

            break;

        case ExpressionType::GREATERTHAN_OR_EQUAL:

            return $this->_prepareBinaryExpression(

                self::GREATERTHAN_OR_EQUAL, $left, $right

            );

            break;

        case ExpressionType::LESSTHAN:

            return $this->_prepareBinaryExpression(self::LESSTHAN, $left, $right);

            break;

        case ExpressionType::LESSTHAN_OR_EQUAL:

            return $this->_prepareBinaryExpression(

                self::LESSTHAN_OR_EQUAL, $left, $right

            );

            break;

        case ExpressionType::EQUAL:

            return $this->_prepareBinaryExpression(self::EQUAL, $left, $right);

            break;

        case ExpressionType::NOTEQUAL:

            return $this->_prepareBinaryExpression(self::NOTEQUAL, $left, $right);

            break;

        default:

            throw new \InvalidArgumentException('onArithmeticExpression');

        }

    }

onUnaryExpression(iType $type, $child)

    /**

     * Call-back for unary expression

     *

     * @param ExpressionType $expressionType The type of unary expression

     * @param string         $child          The child expression

     *

     * @return string

     */

    public function onUnaryExpression($expressionType, $child)

    {

        switch($expressionType) {

        case ExpressionType::NEGATE:

            return $this->_prepareUnaryExpression(self::NEGATE, $child);

            break;

        case ExpressionType::NOT_LOGICAL:

            return $this->_prepareUnaryExpression(self::LOGICAL_NOT, $child);

            break;

        default:

            throw new \InvalidArgumentException('onUnaryExpression');

        }

    }

This function replaces all the unary operators (used in the filtering criteria)  by corresponding unary  operators available in the underlying data-source.

 
onPropertyAccessExpression($expression)

    /**

     * Call-back for property access expression

     *

     * @param PropertyAccessExpression $expression The property access expression

     *

     * @return string

     */

    public function onPropertyAccessExpression($expression)

    {

        $parent = $expression;

        $variable = null;

       

        do {

            $variable

                = $parent->getResourceProperty()->getName()

                . self::MEMBERACCESS

                . $variable;

            $parent = $parent->getParent();

        } while ($parent != null);

 

        $variable = rtrim($variable, self::MEMBERACCESS);

        $variable = $this->getIteratorName() . self::MEMBERACCESS . $variable;

        return $variable;

    }

onFunctionCallExpression($expression)

This function replaces all the OData function calls in filtering criteria by their corresponding functions available in the underlying data-source.

This function depends on the underlying DB so its implementation for one data source may be different than the implementation for other data source.

Implementation of onFunctionCallExpression for MySQL

    /**

     * Call-back for function call expression

     *

     * @param FunctionDescription $functionDescription Description of the function.

     * @param array<string>       $params              Paameters to the function.

     *

     * @return string

     */

    public function onFunctionCallExpression($functionDescription, $params)

    {

        switch($functionDescription->functionName) {

        case ODataConstants::STRFUN_COMPARE:

            return "STRCMP($params[0], $params[1])";

            break;

        case ODataConstants::STRFUN_ENDSWITH:

            return "(STRCMP($params[1],RIGHT($params[0],LENGTH($params[1]))) = 0)";

            break;

        case ODataConstants::STRFUN_INDEXOF:

            return "INSTR($params[0], $params[1])";

            break;

        case ODataConstants::STRFUN_REPLACE:

            return "REPLACE($params[0],$params[1],$params[2])";

            break;

        case ODataConstants::STRFUN_STARTSWITH:

            return "(STRCMP($params[1],LEFT($params[0],LENGTH($params[1]))) = 0)";

            break;

        case ODataConstants::STRFUN_TOLOWER:

            return "LOWER($params[0])";

            break;

        case ODataConstants::STRFUN_TOUPPER:

            return "UPPER($params[0])";

            break;

        case ODataConstants::STRFUN_TRIM:

            return "TRIM($params[0])";

            break;

        case ODataConstants::STRFUN_SUBSTRING:

            return count($params) == 3 ?

                "SUBSTRING($params[0], $params[1], $params[2])" :

                "SUBSTRING($params[0], $params[1])";

            break;

        case ODataConstants::STRFUN_SUBSTRINGOF:

            return "(LOCATE($params[0], $params[1]) > 0)";

            break;

        case ODataConstants::STRFUN_CONCAT:

            return CONCAT($params[0],$params[1]);

            break;

        case ODataConstants::STRFUN_LENGTH:

            return "LENGTH($params[0])";

            break;

        case ODataConstants::GUIDFUN_EQUAL:

            return self::TYPE_NAMESPACE . "Guid::guidEqual($params[0], $params[1])";

            break;

        case ODataConstants::DATETIME_COMPARE:

            return

                self::TYPE_NAMESPACE

                    . "DateTime::dateTimeCmp($params[0], $params[1])";

            break;

        case ODataConstants::DATETIME_YEAR:

            return "EXTRACT(YEAR from ".$params[0].")";

            break;

        case ODataConstants::DATETIME_MONTH:

            return "EXTRACT(MONTH from ".$params[0].")";

            break;

        case ODataConstants::DATETIME_DAY:

            return "EXTRACT(DAY from ".$params[0].")";

            break;

        case ODataConstants::DATETIME_HOUR:

            return "EXTRACT(HOUR from ".$params[0].")";

            break;

        case ODataConstants::DATETIME_MINUTE:

            return "EXTRACT(MINUTE from ".$params[0].")";

            break;

        case ODataConstants::DATETIME_SECOND:

            return "EXTRACT(SECOND from ".$params[0].")";

            break;               

        case ODataConstants::MATHFUN_ROUND:

            return "ROUND($params[0])";

            break;

        case ODataConstants::MATHFUN_CEILING:

            return "CEIL($params[0])";

            break;

        case ODataConstants::MATHFUN_FLOOR:

            return "FLOOR($params[0])";

            break;

        case ODataConstants::BINFUL_EQUAL:

            return

                self::TYPE_NAMESPACE

                    . "Binary::binaryEqual($params[0], $params[1])";

            break;

        case 'is_null':

            return "is_null($params[0])";

            break;

           

        default:

            throw new \InvalidArgumentException('onFunctionCallExpression');

        }

    }

At the end, we have to define following two methods to generate an expression with using of operands and operator.

    /**

     * To format binary expression

     *

     * @param string $operator The binary operator.

     * @param string $left     The left operand.

     * @param string $right    The right operand.

     *

     * @return string

     */

    private function _prepareBinaryExpression($operator, $left, $right)

    {

        return self::OPEN_BRAKET . $left . ' ' . $operator . ' ' . $right . self::CLOSE_BRACKET;

    }

 

    /**

     * To format unary expression

     *

     * @param string $operator The unary operator.

     * @param string $child    The operand.

     *

     * @return string

     */

    private function _prepareUnaryExpression($operator, $child)

    {

        return $operator . self::OPEN_BRAKET . $child . self::CLOSE_BRACKET;

    }

 

Implementation of onFunctionCallExpression for MS-SQL server

    /**

     * Call-back for function call expression

     *

     * @param FunctionDescription $functionDescription Description of the function.

     * @param array<string>       $params              Paameters to the function.

     *

     * @return string

     */

    public function onFunctionCallExpression($functionDescription, $params)

    {

        switch($functionDescription->functionName) {

        case ODataConstants::STRFUN_COMPARE:

            return "STRCMP($params[0]; $params[1])";

            break;

        case ODataConstants::STRFUN_ENDSWITH:

            return "STRCMP($params[1]; RIGHT($params[0], LEN($params[1])))";

            break;

        case ODataConstants::STRFUN_INDEXOF:

            return "CHARINDEX($params[1], $params[0])";

            break;

        case ODataConstants::STRFUN_REPLACE:

            return "REPLACE($params[0],$params[1],$params[2])";

            break;

        case ODataConstants::STRFUN_STARTSWITH:

            return "STRCMP($params[1]; LEFT($params[0],LEN($params[1])))";

            break;

        case ODataConstants::STRFUN_TOLOWER:

            return "LOWER($params[0])";

            break;

        case ODataConstants::STRFUN_TOUPPER:

            return "UPPER($params[0])";

            break;

        case ODataConstants::STRFUN_TRIM:

            return "TRIM($params[0])";

            break;

        case ODataConstants::STRFUN_SUBSTRING:

            if (count($params) == 3) {

                return "SUBSTRING($params[0], $params[1]+1, $params[2])";

            } else {

                throw new \InvalidArgumentException("onFunctionCallExpression_Substring_needs_3_Parameters");

            }

            break;

        case ODataConstants::STRFUN_SUBSTRINGOF:

            return "PATINDEX('%'+$params[0]+'%', $params[1])";

            break;

        case ODataConstants::STRFUN_CONCAT:

            return "$params[0] + $params[1]";

            break;

        case ODataConstants::STRFUN_LENGTH:

            return "LEN($params[0])";

            break;

        case ODataConstants::GUIDFUN_EQUAL:

            return self::TYPE_NAMESPACE . "Guid::guidEqual($params[0], $params[1])";

            break;

        case ODataConstants::DATETIME_COMPARE:

            return

                self::TYPE_NAMESPACE

                    . "DateTime::dateTimeCmp($params[0], $params[1])";

            break;

        case ODataConstants::DATETIME_YEAR:

            return "YEAR($params[0])";

            break;

        case ODataConstants::DATETIME_MONTH:

            return "MONTH($params[0])";

            break;

        case ODataConstants::DATETIME_DAY:

            return "DAY( $params[0] )";

            break;

        case ODataConstants::DATETIME_HOUR:

            return "DATENAME(HOUR, $params[0])";

            break;

        case ODataConstants::DATETIME_MINUTE:

            return "DATENAME(MINUTE, $params[0])";

            break;

        case ODataConstants::DATETIME_SECOND:

            return "DATENAME(SECOND, $params[0])";

            break;               

        case ODataConstants::MATHFUN_ROUND:

            return "ROUND($params[0])";

            break;

        case ODataConstants::MATHFUN_CEILING:

            return "CEIL($params[0])";

            break;

        case ODataConstants::MATHFUN_FLOOR:

            return "FLOOR($params[0])";

            break;

        case ODataConstants::BINFUL_EQUAL:

            return

                self::TYPE_NAMESPACE

                    . "Binary::binaryEqual($params[0], $params[1])";

            break;

        case 'is_null':

            return "is_null($params[0])";

            break;

        default:

            throw new \InvalidArgumentException('onFunctionCallExpression');

        }

}

OData library expects that all kind of string comparisons should be returned in form of STRCMP(PARAM1,PARAM2) and if string comparison is possible by another way like by using ‘=’ operator then also we have to return in the same fashion and we have to manipulate _prepareBinayExpression() to handle the string comparison as per the underlying data source.

        switch($functionDescription->functionName) {

        case ODataConstants::STRFUN_COMPARE:

            return "STRCMP($params[0]; $params[1])";

            break;

        case ODataConstants::STRFUN_ENDSWITH:

            return "STRCMP($params[1]; RIGHT($params[0], LEN($params[1])))";

            break;

        case ODataConstants::STRFUN_STARTSWITH:

            return "STRCMP($params[1]; LEFT($params[0],LEN($params[1])))";

            break;

Above implementation for MSSQL-Server shows it as MS-SQL server supports string comparison directly even though we are returning STRCMP function call.

So as I explained that we have to handle string comparison separately for MSSQL-Server hence we have to define one more methods in NorthWindDSExpressionProvider.php.

    /**

     * To format binary expression

     *

     * @param string $operator The binary operator.

     * @param string $left     The left operand.

     * @param string $right    The right operand.

     *

     * @return string

     */

    private function _prepareBinaryExpression($operator, $left, $right)

    {

      if (!substr_compare($left, "STRCMP", 0, 6)) {

            $str = explode(';', $left, 2);

            $str[0] = str_replace('STRCMP', '', $str[0]);

            if ($right == 'false' and $right != '0') {

                  if (!substr_compare($operator, '!', 0, 1)) {

                      $operator = str_replace('!', '', $operator);

                  } else if ($operator == '>=') {

                      $operator = '<';

                  } else if ($operator == '<=') {

                      $operator = '>';

                  } else {

                    $operator = "!".$operator;

                  }

                  return self::OPEN_BRAKET

                    . $str[0] . ' ' . $operator

                    . ' ' . $str[1] . self::CLOSE_BRACKET;

            } else {

                return self::OPEN_BRAKET

                    . $str[0] . ' ' . $operator

                    . ' ' . $str[1] . self::CLOSE_BRACKET;

            }

        }

        //In MSSQL Server Index starts from 1 instead of 0.

        if (!substr_compare($left, "CHARINDEX", 0, 9)) {

            $right++;

        }

        if (!substr_compare($left, "PATINDEX", 0, 8)) {

            if ($right == 'true') {

                if (!substr_compare($operator, '!', 0, 1)) {

                      $operator = str_replace('!', '', $operator);

                  } else if ($operator == '>=') {

                      $operator = '<';

                  } else if ($operator == '<=') {

                      $operator = '>';

                  } else {

                    $operator = "!".$operator;

                  }

            }

            $right = '0';

        }

        return

            self::OPEN_BRAKET . $left . ' ' . $operator . ' ' . $right . self::CLOSE_BRACKET;

    }

 

    /**

     * To format unary expression

     *

     * @param string $operator The unary operator.

     * @param string $child    The operand.

     *

     * @return string

     */

    private function _prepareUnaryExpression($operator, $child)

    {

        return $operator . self::OPEN_BRAKET . $child . self::CLOSE_BRACKET;

    }

Metadata Mapping

If we implemented IDataServiceQueryProvider2  and property names defined in the Metadata are not matching with the names defined in the underlying data source then we have to define one more method CreateNorthWindMetaData::mappingInitialize() because library has to generate an expression for filtering criteria and this expression will contain few conditions and library has to replace all the property-names appearing in the expression by their actual name which are existing in the DB.

CreateNorthWindMetaData::mappingInitialize() is not required  in multiple senses:

·         If we are not  implementing IDataServiceQueryProvider2.

·         If  we are implementing IDataServiceQueryProvider2 but the property and entity names defined in the metadata are same as defined in the data-source and we are not using any name alias in our queries.

So first end-developer has to define the mapping first and then library uses the defined mapping info for generating the proper expression and append it to the SQL query before firing the query.

We have to use object of MetadataMapping class to define the mapping between properties names defined in the metadata and column names which are already existing in the DB.

Member methods

This class has few functions but only 2 functions are required for defining the metadata mapping. Other functions are required for library to extract the mapping info defined by the end-developer.

Name

Parameters

Return Type

Description

Comments

mapEntity()

$entityName(resource-set name defined in the metadata definition.Type :String,MUST)

$mappedEntityName(Table-name defined in the DB schema for that resource-set,Type:String,MUST)

 

String

This function maps the resource set name which is defined in the metadata to the table name which is physically existing in the DB.

NIL

mapProperty()

$entityName(resource-set name defined in the metadata definition.Type :String,MUST)

$metaPropertyName(Entity name defined in the metadata,Type:String,MUST)

$dsPropertyName(Corresponding field name defined in the DB schema,Type:String,MUST)

String

This function maps the property name which is defined in the metadata for one particular resource to the fields name which is physically existing in the DB.

NIL

We have one sample implementation of MetadataMapping for WordPress service because its implementation is not required for NorthWind DB because names defined in the Metada for northwind service are same as defined in the database schema.

    public static function mappingInitialize() {

      $mappedDetails = new MetadataMapping();

      $mappedDetails->mapEntity('Posts', 'wp_posts');

      $mappedDetails->mapProperty('Posts', 'PostID', 'ID');

      $mappedDetails->mapProperty('Posts', 'Author', 'post_author');

      $mappedDetails->mapProperty('Posts', 'Date', 'post_date');

      $mappedDetails->mapProperty('Posts', 'DateGmt', 'post_date_gmt');

      $mappedDetails->mapProperty('Posts', 'Content', 'post_content');

      $mappedDetails->mapProperty('Posts', 'Title', 'post_title');

      $mappedDetails->mapProperty('Posts', 'Excerpt', 'post_excerpt');

      $mappedDetails->mapProperty('Posts', 'Status', 'post_status');

      $mappedDetails->mapProperty('Posts', 'CommentStatus', 'comment_status');

      $mappedDetails->mapProperty('Posts', 'PingStatus', 'ping_status');

      $mappedDetails->mapProperty('Posts', 'Password', 'post_password');

      $mappedDetails->mapProperty('Posts', 'Name', 'post_name');

      $mappedDetails->mapProperty('Posts', 'ToPing', 'to_ping');

      $mappedDetails->mapProperty('Posts', 'Pinged', 'pinged');

        $mappedDetails->mapProperty('Posts', 'ModifiedGmt', 'post_modified_gmt');

        $mappedDetails->mapProperty('Posts', 'ContentFiltered', 'post_content_filtered');

        $mappedDetails->mapProperty('Posts', 'ParentID', 'post_parent');

        $mappedDetails->mapProperty('Posts', 'Guid', 'guid');

        $mappedDetails->mapProperty('Posts', 'MenuOrder', 'menu_order');

        $mappedDetails->mapProperty('Posts', 'Type', 'post_type');

        $mappedDetails->mapProperty('Posts', 'MimeType', 'post_mime_type');

        $mappedDetails->mapProperty('Posts', 'CommentCount', 'comment_count');

     

      $mappedDetails->mapEntity('Tags', 'wp_terms');

      $mappedDetails->mapProperty('Tags', 'TagID', 't.term_id');

      $mappedDetails->mapProperty('Tags', 'Name', 't.name');

      $mappedDetails->mapProperty('Tags', 'Slug', 't.slug');

      $mappedDetails->mapProperty('Tags', 'Description', 'tt.description');

                       

      $mappedDetails->mapEntity('Categories', 'wp_terms');

      $mappedDetails->mapProperty('Categories', 'CategoryID', 't.term_id');

      $mappedDetails->mapProperty('Categories', 'Name', 't.name');

      $mappedDetails->mapProperty('Categories', 'Slug', 't.slug');

      $mappedDetails->mapProperty('Categories', 'Description', 'tt.description');

     

      $mappedDetails->mapEntity('Comments', 'wp_comments');

      $mappedDetails->mapProperty('Comments', 'CommentID', 'comment_id');

      $mappedDetails->mapProperty('Comments', 'PostID', 'comment_post_id');

      $mappedDetails->mapProperty('Comments', 'Author', 'comment_author');

      $mappedDetails->mapProperty('Comments', 'AuthorEmail', 'comment_author');

      $mappedDetails->mapProperty('Comments', 'AuthorUrl', 'comment_author_url');

      $mappedDetails->mapProperty('Comments', 'AuthorIp', 'comment_author_email');

      $mappedDetails->mapProperty('Comments', 'DateGmt', 'comment_date');

      $mappedDetails->mapProperty('Comments', 'Content', 'comment_content');

      $mappedDetails->mapProperty('Comments', 'Karma', 'comment_karma');

      $mappedDetails->mapProperty('Comments', 'Approved', 'comment_approved');

      $mappedDetails->mapProperty('Comments', 'Agent', 'comment_agent');

      $mappedDetails->mapProperty('Comments', 'Type', 'comment_type');

      $mappedDetails->mapProperty('Comments', 'ParentID', 'comment_parent');

      $mappedDetails->mapProperty('Comments', 'UserID', 'user_id');                                                             

     

      $mappedDetails->mapEntity('Users', 'wp_users');

      $mappedDetails->mapProperty('Users', 'UserID', 'ID');

      $mappedDetails->mapProperty('Users', 'Login', 'user_login');

      $mappedDetails->mapProperty('Users', 'Nicename', 'user_nicename');

      $mappedDetails->mapProperty('Users', 'Email', 'user_email');

      $mappedDetails->mapProperty('Users', 'Url', 'user_url');

      $mappedDetails->mapProperty('Users', 'Registered', 'user_registered');

      $mappedDetails->mapProperty('Users', 'Status', 'user_status');

      $mappedDetails->mapProperty('Users', 'DisplayName', 'display_name');

      return $mappedDetails;

    }

 

      $mappedDetails->mapEntity('Tags', 'wp_terms');

      $mappedDetails->mapProperty('Tags', 'TagID', 't.term_id');

      $mappedDetails->mapProperty('Tags', 'Name', 't.name');

      $mappedDetails->mapProperty('Tags', 'Slug', 't.slug');

      $mappedDetails->mapProperty('Tags', 'Description', 'tt.description');   

Above example shows that if we are using Join for any resource-set or using table-name alias in our query  then also we can define this by metadata mapping .

We have to call this method after creating the instance of metadata provider.We have implementation of IServiceProvider i.e. NorthWindDataService.php and getService() will call this method.

    public function getService($serviceType)

    {

      if(($serviceType === 'IDataServiceMetadataProvider') ||

            ($serviceType === 'IDataServiceQueryProvider2') ||

            ($serviceType === 'IDataServiceStreamProvider')) {

            if (is_null($this->_wordPressExpressionProvider)) {

                        $this->_wordPressExpressionProvider = new WordPressDSExpressionProvider();                     

            }   

      }

        if ($serviceType === 'IDataServiceMetadataProvider') {

            if (is_null($this->_wordPressMetadata)) {

                $this->_wordPressMetadata = CreateWordPressMetadata::create();

                $this->_wordPressMetadata->mappedDetails = CreateWordPressMetadata::mappingInitialize();

            }

            return $this->_wordPressMetadata;

        }

Changes in the service.config FILE

We have to modify the “Services\service.config.xml” file to register the service with the OData Producer for PHP so that our library can generate the OData feeds for the specified service.

Make sure that <path> and <baseURL> tag contains proper value in the configuration file ( D:\Projects\ODataPHPProducer\Services\service.config.xml)  :

   i.                  <path>                 :  The absolute/relative  path of  the ‘NorthWindDataService.php’

 ii.                  <baseURL>         : The base url to the web site you configured in step 3 followed by

‘NorthWind.svc.

Make sure that the providers which we are creating for our service

We can specify the relative path also in the configuration file.

Configuring the OData Service Parameter

Developer can define his service configuration within the initializeService method of data service class. Implementation of IserviceProvider will contain definition of InitializeService method.

This method will be invoked after the dispatcher creates an instance of data service class. This method also accept one parameter of type DataServiceConfiguration, developer can use this parameter to configure the service.

This section will explain about the DataServiceConfiguration class which is implementing IDataServiceConfiguration interface.

Member Methods

Name

Parameters

Return Type

Description

getMaxExpandCount()

 Void

Integer

Gets maximum number of segments to be expanded allowed in a request.

setMaxExpandCount()

$maxExpandCount (Type : Integer, MUST)

Void

Sets maximum number of segments to be expanded allowed in a request.

getMaxExpandDepth()

 Void

Integer

Gets the maximum number of segments in a single $expand path.

setMaxExpandDepth()

$maxExpandDepth (Type : Integer, MUST)

Void

Sets the maximum number of segments in a single $expand path.

getMaxResultsPerCollection()

 Void

Integer

Gets maximum number of elements in each returned collection.

setMaxresultsPerCollection()

$maxResultPerCollection(Type : Integer,MUST)

Void

Sets maximum number of elements in each returned collection .

getUseVerboseErrors()

Void

Boolean

Gets whether verbose errors should be used by default.

setUseVerboseErrors()

$useVerboseError(Type: Boolean,MUST)

Void

Sets whether verbose errors should be used by default.

getEntitySetAccessRule()

$resourceSet(Type: , MUST)

EntitySetRights

Gets the access rights on the specified resource set.

setEntitySetAccessRule()

$name(Type:String, MUST)

$rights(Type: EntitySetRights,MUST)

Void

Sets the access rights on the specified resource set.

getEntitySetPageSize()

$resourceSet(Type: Object, MUST)

Integer

Gets the maximum page size for an entity set resource.

setEntitySetPageSize()

$name(Type:String, MUST)

$pageSize(Type: Integer,MUST)

Void

Sets the maximum page size for an entity set resource.

getAcceptCountRequests()

Void

Boolean

Gets whether requests with the $count path segment or the $inlinecount query  options are accepted.

setAcceptCountRequests()

$acceptCountRequest(Type: Boolean,MUST)

Void

Sets whether requests with the $count path segment or the $inlinecount query  options are accepted.

getAcceptProjectionRequests()

Void

Boolean

Gets whether projection requests ($select) should be accepted.

setAcceptProjectionRequests()

$acceptProjectionRequest(Type:Boolean,MUST)

Void

Sets whether projection requests ($select) should be accepted.

getMaxDataServiceVersion()

Void

Integer

Gets maximum version of the response sent by server.

getMaxDataServiceVersionObject()

Void

Object of Version class

Gets Maximum version of the response sent by server.

setMaxDataServiceVersion()

Integer

Void

Sets maximum version of the response sent by server

How to set configuration parameters - An Example

This section shows examples that how we can set configuration parameters for a service.

//This method is called only once to initialize service-wide policies

public static function initializeService(DataServiceConfiguration &$config)

{

    $config->setEntitySetPageSize('*', 5); //Configure the page size

    $config->setEntitySetAccessRule('*', EntitySetRights::ALL);

    $config->setAcceptCountRequests(true);

    $config->setAcceptProjectionRequests(true);

    //Configure the maxDataServiceVesion header value which should be take care to generate the response

    $config->setMaxDataServiceVersion(DataServiceProtocolVersion::V3);

}

This function we need to define in the implementation of the IserviceProvider interface.

Entity set Rights

·.NET Framework 3.5

An enumeration used to define access rights to data that is deployed by Data Services.This enumeration bitwise combination of its member values.

The EntitySetRight enumeration is used to specify access rights for entity set resources that are available on the data service.

This enumeration is used as second argument of DataServiceConfiguration::setEntitySetAccessRule API.

DataServiceConfiguration::SetEntitySetAccessRule(name, rights)

·         name: Name of the entity set for which to set access rights, an asterisk (*) value can be supplied for the name parameter to set access for all remaining entity sets to the same level.

·         rights: One of EntitySetRights enum value specifies the access rights to be granted to this entity set

Member name

Description

None

Denies all rights to access data.

ReadSingle

Authorization to read single data items.

ReadMultiple

Authorization to read sets of data.

WriteAppend

Authorization to create new data items in data sets.

WriteReplace

Authorization to replace data.

WriteDelete

Authorization to delete data items from data sets.

WriteMerge

Authorization to merge data.

AllRead

Authorization to read data.

AllWrite

Authorization to write data.

All

Authorization to create, read, update, and delete data.

Usually we use “ALL” from this enumeration as right now we are providing only read-only functions so other values are for further enhancement purpose.

Click here to see the use of “EntitySetRights”.

// Enum for entity set access rights

class EntitySetRightsEnum

{

    public static $None = 0x000000;

    /**

     * Specifies the right to read one resource belonging to this entity set per request.

     */

    public static $ReadSingle = 0x000001;

      /**

     * Specifies the right to read multiple resources belonging to this entity set per request.

     */

    public static $ReadMultiple = 0x000010;

    /**

     * Specifies the right to add (POST) new resources to the entity set. 

     */

    public static $WriteAppend = 0x000100;

    /**

     * Specifies the right to replace (PUT) existing resource in the entity set  

     */

    public static $WriteReplace = 0x001000;

    /**

     * Specifies the right to delete existing resource in the entity set  

     */

    public static $WriteDelete = 0x010000;

      /**

       * Specifies the right to update (MERGE) existing resource in the container   */

    public static $WriteMerge = 0x100000;       

    /**

     * Specifies the right to read single or multiple resources in a single request

     */

    public static $AllRead = 0x000001 | 0x000010;      

    /**

     * Specifies right to perform CUD

     */

    public static $AllWrite = 0x000100 | 0x010000 | 0x001000 | 0x100000;

    /**

     * specifies rights to perform all operations

     */

    public static $All = 0x000100 | 0x010000 | 0x001000 | 0x100000 | 0x000001 | 0x000010;

}

 

By using of Entity Set Rights we can change the visibility of one specific entity or all entities.

For example if we define this function in following-file:Services/NorthWind/NorthWindDataService.php

//This method is called only once to initialize service-wide policies

public static function initializeService(DataServiceConfiguration &$config)

{

    $config->setEntitySetPageSize('*', 5);

    $config->setEntitySetAccessRule('Orders', EntitySetRights::NONE);

    $config->setEntitySetAccessRule('*', EntitySetRights::ALL);       

    $config->setAcceptCountRequests(true);

    $config->setAcceptProjectionRequests(true);

    $config->setMaxDataServiceVersion(DataServiceProtocolVersion::V3);

}

Here even though entity set ‘Orders’ is registered in metadata, it will not appear in edmx as its visibility is NONE and If client tried to access ‘Orders’ Entity set server will through resource not found error.

Now if user try to access “http://localhost:8086/NorthWind.svc/Customers(‘ALFKI’)/Orders then user will get following o/p:

Configuring the OData Services

First assume that developer has the OData library in following path: D:\Projects\ODataPHPProducer

Configuring PHP

Modify the PHP configuration-file:C:\PHP5\php.ini to include the path of the OData Producer library and restart the IIS server:

include_path = “.;D:\Projects\ODataPHPProducer\library”;

 

Configuring Web-Server

IIS

This step is optional and required if we are using IIS as web server:

We have to install URL rewrite module for IIS from following path:

         i.            http://www.iis.net/download/URLRewrite

Please make sure that C:\Projects\ODataPHPProducer\web.config file contains following entry in

Apache2

If developer wants to use this library with apache then we have to modify “.htaccess” file for url-rewrite by following way:

1)      Make sure that /var/www/.htacccess file contains following entry:

<IfModule mod_rewrite.c>

                         RewriteEngine on

                        RewriteRule    (\.svc.*) OData/Index.php

</IfModule>

Here /var/www/OData folder contains the complete library code.

2)      Make sure to add the following entries in the /etc/apache2/httpd.conf file:

php_value include_path "./:/var/www/OData/library"

php_value date.timezone "America/Los_Angeles"          

3)      Make sure that /etc/apach2/apache2.conf file has following entries:

Include /etc/apache2/httpd.conf

Database: Installation and Configuration

SQL-Server

This step is required only if we are going to use SQL-Server as data-source.

We have to install SQLEXPRESS (With instance name ;SQLEXPRESS)

MySQL

This step is required only if we are going to use SQL-Server as data-source.

We can download and install  the MySQL-Server from the following URL: http://dev.mysql.com/downloads/mysql/

Installing the NorthWind DB(For testing purpose)

This step is required only for testing the sample NorthWind service.

We have to install NorthWind database from the following URL:

(http://www.microsoft.com/downloads/en/details.aspx?FamilyID=06616212-0356-46a0-8da2-eebc53a68034)

Configuring the library for NorthWind Services (For testing purpose)

We will take a NorthWind DB example to understand that how to configure the service with the OData Producer for PHP. For instance we are configuring the sample NorthWind services with IIS that listen to the port:”8086”.

Directory Structure

Please make sure that required files for services are available in the service folder as we have files for the sample service:”NorthWind” in /services/NorthWindDB

NOTE : Here last one file “NorthWindStreamProvider.php” is an optional file and we need to create this file only if we want to expose some stream data by using of “OData Producer for PHP”.

Here if we are implementing IDataServiceQueryProvider2 then we have to implement IExpressionProvider also and service folder services\NorthWind will contain following files:

Configuring QueryProvider to connect to underlying DB
IIS

Open ‘services\NorthWind\NorthWindQueryProvider.php’ and update ‘SERVER’ constant with your SQL server instance name

MySQL

We can modify this QueryProvider.php file For MySQL we can modify this file like following way

Creating and Configuring the Web-Site(Required only for IIS)

This step is required only for IIS.

Create a Web-Site in IIS with physical path-points to the framework folder.

Now restart the Web-Site.

Making-Sure that Everything is working fine

We can’t test the things until and unless we implement all the required providers and configure the Web-Server.

After implementing the provider and configuring the service and also after configuring the Web-Server we can start testing to make sure that whether we implemented the provider in the right way or not.

·         First we have to set-up one web-site if you are using IIS.

·         Then we have to configure the Web-Server for URL-Rewrite mode so that if user mentioned .svc in the URL then request should reached to our Index.php file.

·         For instance if user setup the NorthWind.svc  and if Web-Site is configured for 8086 port on localhost

o   If user request for  http://localhost:8086/NorthWind.svc then library should return the service document.

o   If user request for http://localhost:8086/NorthWind.svc/$metadata then user should get metadata document which will explain the entities and properties of each entity and their relationships.

o   If user request for http://localhost:8086/NorthWind.svc/Customers then he should get list of 5 customers if page-size is configured as 5 in the implementation of IserviceProvider.

If user does something wrong in the implementation of provider then library will throw serialized error everytime.

o   If User is using IE then everytime he will get generalized error message from IE and he can check the exact error message in Mozilla/Firefox.

o   For checking the exact error message in IE user has to do following changes in the IE configuration:

§  Access IE->InternetOptions->Advanced

§  Uncheck “Show Friendly HTTP Error-Message”

Accessing the Service

The service has been configured and now you can browse the service: